package com.fduss.mazegame;

import com.fduss.mazegame.client.ClientChoice;
import com.fduss.mazegame.client.Language;
import com.fduss.mazegame.client.gui.fxgui.Theme;
import com.fduss.mazegame.server.*;
import com.fduss.mazegame.util.Array;

/**
 * This class holds all the options available from the command line.
 * <p>
 * It gets initialized with the application defaults and the process
 * the command line array of strings to configure each option.
 */
public class CLOptions {

    static final Language DEFAULT_LANGUAGE = Language.CHINESE;
    static final int DEFAULT_ROWS = 21;
    static final int DEFAULT_COLS = 35;
    static final int DEFAULT_TRAIL_CAPACITY = 2;
    static final Theme DEFAULT_THEME = Theme.grass;
    static final ClientChoice DEFAULT_CLIENT_CHOICE = ClientChoice.STORYTUI;
    private static CLOptions options = null;
    private Language language;
    private ServerSpec serverSpec;
    private int rows;
    private int cols;
    private int trailCapacity;
    private Theme theme;
    private ClientChoice clientChoice;

    // the set of default options
    public CLOptions() {
        language = DEFAULT_LANGUAGE;
        rows = DEFAULT_ROWS;
        cols = DEFAULT_COLS;
        trailCapacity = DEFAULT_TRAIL_CAPACITY;
        clientChoice = DEFAULT_CLIENT_CHOICE;
        theme = DEFAULT_THEME;
    }

    public CLOptions(String args[]) {
        this(); // init default values.
        if (args.length == 0) {
            if (serverSpec == null) {
                serverSpec = new ServerSpecDepthFirst(
                        rows, cols, trailCapacity);
            }
            return;
        }

        // going throw all the command line arguments
        // again and again is not efficient, but it is simple
        // enough for the students and it is a good chance
        // to build an extraction filter.
        args = extractAndSetClientChoice(args);
        args = extractAndSetLanguage(args);
        args = extractAndSetNumRows(args);
        args = extractAndSetNumCols(args);
        args = extractAndSetTheme(args);
        args = extractAndSetTrailCapacity(args);
        // serverSpec detection comes last to use the
        // appropriate rows, columns and trail capacity.
        args = extractAndSetServerSpec(args);

        if (serverSpec == null) {
            serverSpec = new ServerSpecDepthFirst(
                    rows, cols, trailCapacity);
        }

        // check whether the args is valid
        if (args.length != 0) {
            throw new IllegalArgumentException(
                    "Unrecognized argument: " + args[0]);
        }
    }

    public static CLOptions getInstance(String[] args) {
        if (options == null)
            options = new CLOptions(args);
        return options;
    }

    private String[] extractAndSetClientChoice(String[] args) {
        int i = Array.firstIndexOf(Opts.UI.toString(), args, 0);
        if (i == -1) {
            return args;
        }
        // if no more arguments, complain about a missing argument
        if (i + 1 == args.length) {
            throw new IllegalArgumentException(
                    "Missing " + Opts.UI.toString() + " argument" );
        }
        // check if the language is supported
        ClientChoice[] clientChoices = ClientChoice.values();
        boolean supported = false;
        for (ClientChoice clientChoice1 : clientChoices) {
            if (clientChoice1.toString().equals(args[i + 1])) {
                supported = true;
                this.clientChoice = clientChoice1;
                break;
            }
        }
        if (!supported) {
            throw new IllegalArgumentException(
                    "Unrecognized client choice: " + args[i + 1]);
        }
        // check for repeated command
        int repeated = Array.firstIndexOf(Opts.UI.toString(),
                args, i + 2);
        if (repeated != -1) {
            throw new IllegalArgumentException(
                    "Repeated option " + Opts.UI.toString());
        }
        return Array.remove(args, i, 2);
    }

    private String[] extractAndSetLanguage(String[] args) {
        // find if there is an "-l" option
        int i = Array.firstIndexOf(Opts.LANG.toString(), args, 0);
        if (i == -1) {
            return args;
        }
        // if no more arguments, complain about a missing argument
        if (i + 1 == args.length) {
            throw new IllegalArgumentException(
                    "Missing " + Opts.LANG.toString() + " argument" );
        }
        // check if the language is supported
        Language[] langs = Language.values();
        boolean supported = false;
        for (Language lang : langs) {
            if (lang.toString().equals(args[i + 1])) {
                supported = true;
                this.language = lang;
                break;
            }
        }
        if (!supported) {
            throw new IllegalArgumentException(
                    "Unrecognized language: " + args[i + 1]);
        }
        // check for repeated command
        int repeated = Array.firstIndexOf(Opts.LANG.toString(),
                args, i + 2);
        if (repeated != -1) {
            throw new IllegalArgumentException("Repeated option " + Opts.LANG.toString());
        }
        return Array.remove(args, i, 2);
    }

    private String[] extractAndSetServerSpec(String[] args) {
        int i = Array.firstIndexOf(Opts.SPEC.toString(), args, 0);
        if (i == -1) {
            return args;
        }
        // if no more arguments, complain about a missing argument
        if (i + 1 == args.length) {
            throw new IllegalArgumentException(
                    "Missing " + Opts.SPEC.toString() + " argument" );
        }
        // check if the spec is among the supported ones
        switch (args[i + 1]) {
            case "TEST":
                serverSpec = new ServerSpecTest(trailCapacity);
                break;
            case "EMPTY":
                serverSpec =
                        new ServerSpecEmpty(rows, cols, trailCapacity);
                break;
            case "PRIM":
                serverSpec =
                        new ServerSpecPrim(rows, cols, trailCapacity);
                break;
            case "DEPTHFIRST":
                serverSpec =
                        new ServerSpecDepthFirst(rows, cols, trailCapacity);
                break;
            default:
                throw new IllegalArgumentException(
                        "Unrecognized spec: " + args[i + 1]);
        }
        // check for repeated command
        int repeated = Array.firstIndexOf(Opts.SPEC.toString(),
                args, i + 2);
        if (repeated != -1) {
            throw new IllegalArgumentException("Repeated option " + Opts.SPEC.toString());
        }
        return Array.remove(args, i, 2);
    }

    private String[] extractAndSetNumRows(String[] args) {
        int i = Array.firstIndexOf(Opts.ROWS.toString(), args, 0);
        if (i == -1) {
            return args;
        }
        // if no more arguments, complain about a missing argument
        if (i + 1 == args.length) {
            throw new IllegalArgumentException(
                    "Missing " + Opts.ROWS.toString() + " argument" );
        }
        // check if the number of rows is supported
        try {
            rows = Integer.parseInt(args[i + 1]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(
                    "Unrecognized number format: " + args[i + 1]);
        }
        if (rows < 0) {
            throw new IllegalArgumentException(
                    "Negative number of rows: " + args[i + 1]);
        }
        // check for repeated command
        int repeated = Array.firstIndexOf(Opts.ROWS.toString(),
                args, i + 2);
        if (repeated != -1) {
            throw new IllegalArgumentException("Repeated option " + Opts.ROWS.toString());
        }
        return Array.remove(args, i, 2);
    }

    private String[] extractAndSetNumCols(String[] args) {
        int i = Array.firstIndexOf(Opts.COLS.toString(), args, 0);
        if (i == -1) {
            return args;
        }
        // if no more arguments, complain about a missing argument
        if (i + 1 == args.length) {
            throw new IllegalArgumentException(
                    "Missing " + Opts.COLS.toString() + " argument" );
        }
        // check if the number of rows is supported
        try {
            cols = Integer.parseInt(args[i + 1]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(
                    "Unrecognized number format: " + args[i + 1]);
        }
        if (cols < 0) {
            throw new IllegalArgumentException(
                    "Negative number of columns: " + args[i + 1]);
        }
        // check for repeated command
        int repeated = Array.firstIndexOf(Opts.COLS.toString(),
                args, i + 2);
        if (repeated != -1) {
            throw new IllegalArgumentException("Repeated option " + Opts.COLS.toString());
        }
        return Array.remove(args, i, 2);
    }

    private String[] extractAndSetTheme(String[] args) {
        int i = Array.firstIndexOf(Opts.THEME.toString(), args, 0);
        if (i == -1) {
            return args;
        }
        // if no more arguments, complain about a missing argument
        if (i + 1 == args.length) {
            throw new IllegalArgumentException(
                    "Missing " + Opts.THEME.toString() + " argument" );
        }
        // check if the theme is supported
        Theme[] themes = Theme.values();
        boolean supported = false;
        for (Theme theme1 : themes) {
            if (theme1.toString().equals(args[i + 1])) {
                supported = true;
                this.theme = theme1;
                break;
            }
        }
        if (!supported) {
            throw new IllegalArgumentException(
                    "Unrecognized language: " + args[i + 1]);
        }
        // check for repeated command
        int repeated = Array.firstIndexOf(Opts.COLS.toString(),
                args, i + 2);
        if (repeated != -1) {
            throw new IllegalArgumentException("Repeated option " + Opts.COLS.toString());
        }
        return Array.remove(args, i, 2);
    }

    private String[] extractAndSetTrailCapacity(String[] args) {
        int i = Array.firstIndexOf(Opts.TRAIL.toString(), args, 0);
        if (i == -1) {
            return args;
        }
        // if no more arguments, complain about a missing argument
        if (i + 1 == args.length) {
            throw new IllegalArgumentException(
                    "Missing " + Opts.TRAIL.toString() + " argument" );
        }
        // check if the number of rows is supported
        try {
            trailCapacity = Integer.parseInt(args[i + 1]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(
                    "Unrecognized number format: " + args[i + 1]);
        }
        if (trailCapacity < 0) {
            throw new IllegalArgumentException(
                    "Negative number of trailCapacity: " + args[i + 1]);
        }
        // check for repeated command
        int repeated = Array.firstIndexOf(Opts.TRAIL.toString(),
                args, i + 2);
        if (repeated != -1) {
            throw new IllegalArgumentException("Repeated option " + Opts.TRAIL.toString());
        }
        return Array.remove(args, i, 2);
    }

    public Theme getTheme() {
        return theme;
    }

    public Language getLanguage() {
        return language;
    }

    public ServerSpec getServerSpec() {
        return serverSpec;
    }

    ClientChoice getClientChoice() {
        return clientChoice;
    }

    // enum for parsing the main args
    private enum Opts {
        LANG("-l" ), // followed by ENGLISH or SPANISH
        SPEC("-s" ), // followed by TEST, EMPTY, PRIM, DEPTHFIRST
        ROWS("-r" ), // followed by a number
        COLS("-c" ), // followed by a number
        TRAIL("-tr" ), // followed by a number
        UI("-ui" ), // followed by (ClientChoice) TUI or SWGUI
        THEME("-th" ); // Theme: snow or grass
        private final String text;

        Opts(final String text) {
            this.text = text;
        }

        public String toString() {
            return text;
        }
    }
}
